//===--- DiagnosticEngine.h - Diagnostic Display Engine ---------*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
//  This file declares the DiagnosticEngine class, which manages any diagnostics
//  emitted by Swift.
//
//===----------------------------------------------------------------------===//

#ifndef POLARPHP_BASIC_DIAGNOSTICENGINE_H
#define POLARPHP_BASIC_DIAGNOSTICENGINE_H

#include "polarphp/ast/TypeLoc.h"
#include "polarphp/ast/DeclNameLoc.h"
#include "polarphp/ast/DiagnosticConsumer.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/Support/Allocator.h"
#include "llvm/Support/VersionTuple.h"

namespace polar {

class SourceManager;
class Decl;
class DeclAttribute;
class DiagnosticEngine;
class ValueDecl;
class SourceFile;

enum class PatternKind : uint8_t;
enum class SelfAccessKind : uint8_t;
enum class ReferenceOwnership : uint8_t;
enum class StaticSpellingKind : uint8_t;
enum class DescriptiveDeclKind : uint8_t;
enum DeclAttrKind : unsigned;

/// Enumeration describing all of possible diagnostics.
///
/// Each of the diagnostics described in Diagnostics.def has an entry in
/// this enumeration type that uniquely identifies it.
enum class DiagID : uint32_t;

/// Describes a diagnostic along with its argument types.
///
/// The diagnostics header introduces instances of this type for each
/// diagnostic, which provide both the set of argument types (used to
/// check/convert the arguments at each call site) and the diagnostic ID
/// (for other information about the diagnostic).
template<typename ...ArgTypes>
struct Diag {
   /// The diagnostic ID corresponding to this diagnostic.
   DiagID ID;
};

namespace detail {
/// Describes how to pass a diagnostic argument of the given type.
///
/// By default, diagnostic arguments are passed by value, because they
/// tend to be small. Larger diagnostic arguments
/// need to specialize this class template to pass by reference.
template<typename T>
struct PassArgument {
   typedef T type;
};
}

/// Describes the kind of diagnostic argument we're storing.
///
enum class DiagnosticArgumentKind {
   String,
   Integer,
   Unsigned,
   Identifier,
   ValueDecl,
   Type,
   TypeRepr,
   PatternKind,
   SelfAccessKind,
   ReferenceOwnership,
   StaticSpellingKind,
   DescriptiveDeclKind,
   DeclAttribute,
   VersionTuple,
   LayoutConstraint,
};

namespace diag {
enum class RequirementKind : uint8_t;
}

/// Variant type that holds a single diagnostic argument of a known
/// type.
///
/// All diagnostic arguments are converted to an instance of this class.
class DiagnosticArgument {
   DiagnosticArgumentKind Kind;
   union {
      int IntegerVal;
      unsigned UnsignedVal;
      StringRef StringVal;
      DeclName IdentifierVal;
      ValueDecl *TheValueDecl;
      Type TypeVal;
      TypeRepr *TyR;
      PatternKind PatternKindVal;
      SelfAccessKind SelfAccessKindVal;
      ReferenceOwnership ReferenceOwnershipVal;
      StaticSpellingKind StaticSpellingKindVal;
      DescriptiveDeclKind DescriptiveDeclKindVal;
      const DeclAttribute *DeclAttributeVal;
      llvm::VersionTuple VersionVal;
      LayoutConstraint LayoutConstraintVal;
   };

public:
   DiagnosticArgument(StringRef S)
      : Kind(DiagnosticArgumentKind::String), StringVal(S) {
   }

   DiagnosticArgument(int I)
      : Kind(DiagnosticArgumentKind::Integer), IntegerVal(I) {
   }

   DiagnosticArgument(unsigned I)
      : Kind(DiagnosticArgumentKind::Unsigned), UnsignedVal(I) {
   }

   DiagnosticArgument(DeclName D)
      : Kind(DiagnosticArgumentKind::Identifier), IdentifierVal(D) {}

   DiagnosticArgument(DeclBaseName D)
      : Kind(DiagnosticArgumentKind::Identifier), IdentifierVal(D) {}

   DiagnosticArgument(Identifier I)
      : Kind(DiagnosticArgumentKind::Identifier), IdentifierVal(I) {
   }

   DiagnosticArgument(ValueDecl *VD)
      : Kind(DiagnosticArgumentKind::ValueDecl), TheValueDecl(VD) {
   }

   DiagnosticArgument(Type T)
      : Kind(DiagnosticArgumentKind::Type), TypeVal(T) {
   }

   DiagnosticArgument(TypeRepr *T)
      : Kind(DiagnosticArgumentKind::TypeRepr), TyR(T) {
   }

   DiagnosticArgument(const TypeLoc &TL) {
      if (TypeRepr *tyR = TL.getTypeRepr()) {
         Kind = DiagnosticArgumentKind::TypeRepr;
         TyR = tyR;
      } else {
         Kind = DiagnosticArgumentKind::Type;
         TypeVal = TL.getType();
      }
   }

   DiagnosticArgument(PatternKind K)
      : Kind(DiagnosticArgumentKind::PatternKind), PatternKindVal(K) {}

   DiagnosticArgument(ReferenceOwnership RO)
      : Kind(DiagnosticArgumentKind::ReferenceOwnership),
        ReferenceOwnershipVal(RO) {}

   DiagnosticArgument(SelfAccessKind SAK)
      : Kind(DiagnosticArgumentKind::SelfAccessKind),
        SelfAccessKindVal(SAK) {}

   DiagnosticArgument(StaticSpellingKind SSK)
      : Kind(DiagnosticArgumentKind::StaticSpellingKind),
        StaticSpellingKindVal(SSK) {}

   DiagnosticArgument(DescriptiveDeclKind DDK)
      : Kind(DiagnosticArgumentKind::DescriptiveDeclKind),
        DescriptiveDeclKindVal(DDK) {}

   DiagnosticArgument(const DeclAttribute *attr)
      : Kind(DiagnosticArgumentKind::DeclAttribute),
        DeclAttributeVal(attr) {}

   DiagnosticArgument(llvm::VersionTuple version)
      : Kind(DiagnosticArgumentKind::VersionTuple),
        VersionVal(version) { }

   DiagnosticArgument(LayoutConstraint L)
      : Kind(DiagnosticArgumentKind::LayoutConstraint), LayoutConstraintVal(L) {
   }
   /// Initializes a diagnostic argument using the underlying type of the
   /// given enum.
   template<
      typename EnumType,
      typename std::enable_if<std::is_enum<EnumType>::value>::type* = nullptr>
   DiagnosticArgument(EnumType value)
      : DiagnosticArgument(
      static_cast<typename std::underlying_type<EnumType>::type>(value)) {}

   DiagnosticArgumentKind getKind() const { return Kind; }

   StringRef getAsString() const {
      assert(Kind == DiagnosticArgumentKind::String);
      return StringVal;
   }

   int getAsInteger() const {
      assert(Kind == DiagnosticArgumentKind::Integer);
      return IntegerVal;
   }

   unsigned getAsUnsigned() const {
      assert(Kind == DiagnosticArgumentKind::Unsigned);
      return UnsignedVal;
   }

   DeclName getAsIdentifier() const {
      assert(Kind == DiagnosticArgumentKind::Identifier);
      return IdentifierVal;
   }

   ValueDecl *getAsValueDecl() const {
      assert(Kind == DiagnosticArgumentKind::ValueDecl);
      return TheValueDecl;
   }

   Type getAsType() const {
      assert(Kind == DiagnosticArgumentKind::Type);
      return TypeVal;
   }

   TypeRepr *getAsTypeRepr() const {
      assert(Kind == DiagnosticArgumentKind::TypeRepr);
      return TyR;
   }

   PatternKind getAsPatternKind() const {
      assert(Kind == DiagnosticArgumentKind::PatternKind);
      return PatternKindVal;
   }

   ReferenceOwnership getAsReferenceOwnership() const {
      assert(Kind == DiagnosticArgumentKind::ReferenceOwnership);
      return ReferenceOwnershipVal;
   }

   SelfAccessKind getAsSelfAccessKind() const {
      assert(Kind == DiagnosticArgumentKind::SelfAccessKind);
      return SelfAccessKindVal;
   }

   StaticSpellingKind getAsStaticSpellingKind() const {
      assert(Kind == DiagnosticArgumentKind::StaticSpellingKind);
      return StaticSpellingKindVal;
   }

   DescriptiveDeclKind getAsDescriptiveDeclKind() const {
      assert(Kind == DiagnosticArgumentKind::DescriptiveDeclKind);
      return DescriptiveDeclKindVal;
   }

   const DeclAttribute *getAsDeclAttribute() const {
      assert(Kind == DiagnosticArgumentKind::DeclAttribute);
      return DeclAttributeVal;
   }

   llvm::VersionTuple getAsVersionTuple() const {
      assert(Kind == DiagnosticArgumentKind::VersionTuple);
      return VersionVal;
   }

   LayoutConstraint getAsLayoutConstraint() const {
      assert(Kind == DiagnosticArgumentKind::LayoutConstraint);
      return LayoutConstraintVal;
   }
};

struct DiagnosticFormatOptions {
   const std::string OpeningQuotationMark;
   const std::string ClosingQuotationMark;
   const std::string AKAFormatString;
   const std::string OpaqueResultFormatString;

   DiagnosticFormatOptions(std::string OpeningQuotationMark,
                           std::string ClosingQuotationMark,
                           std::string AKAFormatString,
                           std::string OpaqueResultFormatString)
      : OpeningQuotationMark(OpeningQuotationMark),
        ClosingQuotationMark(ClosingQuotationMark),
        AKAFormatString(AKAFormatString),
        OpaqueResultFormatString(OpaqueResultFormatString) {}

   DiagnosticFormatOptions()
      : OpeningQuotationMark("'"), ClosingQuotationMark("'"),
        AKAFormatString("'%s' (aka '%s')"),
        OpaqueResultFormatString("'%s' (%s of '%s')") {}

   /// When formatting fix-it arguments, don't include quotes or other
   /// additions which would result in invalid code.
   static DiagnosticFormatOptions formatForFixIts() {
      return DiagnosticFormatOptions("", "", "%s", "%s");
   }
};

enum class FixItID : uint32_t;

/// Represents a fix-it defined  with a format string and optional
/// DiagnosticArguments. The template parameters allow the
/// fixIt... methods on InFlightDiagnostic to infer their own
/// template params.
template <typename... ArgTypes> struct StructuredFixIt { FixItID ID; };

/// Diagnostic - This is a specific instance of a diagnostic along with all of
/// the DiagnosticArguments that it requires.
class Diagnostic {
public:
   typedef DiagnosticInfo::FixIt FixIt;

private:
   DiagID ID;
   SmallVector<DiagnosticArgument, 3> Args;
   SmallVector<CharSourceRange, 2> Ranges;
   SmallVector<FixIt, 2> FixIts;
   std::vector<Diagnostic> ChildNotes;
   SourceLoc Loc;
   bool IsChildNote = false;
   const Decl *decl = nullptr;

   friend DiagnosticEngine;

public:
   // All constructors are intentionally implicit.
   template<typename ...ArgTypes>
   Diagnostic(Diag<ArgTypes...> ID,
              typename detail::PassArgument<ArgTypes>::type... VArgs)
      : ID(ID.ID) {
      DiagnosticArgument DiagArgs[] = {
         DiagnosticArgument(0), std::move(VArgs)...
      };
      Args.append(DiagArgs + 1, DiagArgs + 1 + sizeof...(VArgs));
   }

   /*implicit*/Diagnostic(DiagID ID, ArrayRef<DiagnosticArgument> Args)
      : ID(ID), Args(Args.begin(), Args.end()) {}

   // Accessors.
   DiagID getID() const { return ID; }
   ArrayRef<DiagnosticArgument> getArgs() const { return Args; }
   ArrayRef<CharSourceRange> getRanges() const { return Ranges; }
   ArrayRef<FixIt> getFixIts() const { return FixIts; }
   ArrayRef<Diagnostic> getChildNotes() const { return ChildNotes; }
   bool isChildNote() const { return IsChildNote; }
   SourceLoc getLoc() const { return Loc; }
   const class Decl *getDecl() const { return decl; }

   void setLoc(SourceLoc loc) { Loc = loc; }
   void setIsChildNote(bool isChildNote) { IsChildNote = isChildNote; }
   void setDecl(const class Decl *_decl) { decl = _decl; }

   /// Returns true if this object represents a particular diagnostic.
   ///
   /// \code
   /// someDiag.is(diag::invalid_diagnostic)
   /// \endcode
   template<typename ...OtherArgTypes>
   bool is(Diag<OtherArgTypes...> Other) const {
      return ID == Other.ID;
   }

   void addRange(CharSourceRange R) {
      Ranges.push_back(R);
   }

   // Avoid copying the fix-it text more than necessary.
   void addFixIt(FixIt &&F) {
      FixIts.push_back(std::move(F));
   }

   void addChildNote(Diagnostic &&D);
};

/// Describes an in-flight diagnostic, which is currently active
/// within the diagnostic engine and can be augmented within additional
/// information (source ranges, Fix-Its, etc.).
///
/// Only a single in-flight diagnostic can be active at one time, and all
/// additional information must be emitted through the active in-flight
/// diagnostic.
class InFlightDiagnostic {
   friend class DiagnosticEngine;

   DiagnosticEngine *Engine;
   bool IsActive;

   /// Create a new in-flight diagnostic.
   ///
   /// This constructor is only available to the DiagnosticEngine.
   InFlightDiagnostic(DiagnosticEngine &Engine)
      : Engine(&Engine), IsActive(true) { }

   InFlightDiagnostic(const InFlightDiagnostic &) = delete;
   InFlightDiagnostic &operator=(const InFlightDiagnostic &) = delete;
   InFlightDiagnostic &operator=(InFlightDiagnostic &&) = delete;

public:
   /// Create an active but unattached in-flight diagnostic.
   ///
   /// The resulting diagnostic can be used as a dummy, accepting the
   /// syntax to add additional information to a diagnostic without
   /// actually emitting a diagnostic.
   InFlightDiagnostic() : Engine(0), IsActive(true) { }

   /// Transfer an in-flight diagnostic to a new object, which is
   /// typically used when returning in-flight diagnostics.
   InFlightDiagnostic(InFlightDiagnostic &&Other)
      : Engine(Other.Engine), IsActive(Other.IsActive) {
      Other.IsActive = false;
   }

   ~InFlightDiagnostic() {
      if (IsActive)
         flush();
   }

   /// Flush the active diagnostic to the diagnostic output engine.
   void flush();

   /// Add a token-based range to the currently-active diagnostic.
   InFlightDiagnostic &highlight(SourceRange R);

   /// Add a character-based range to the currently-active diagnostic.
   InFlightDiagnostic &highlightChars(SourceLoc Start, SourceLoc End);

   static const char *fixItStringFor(const FixItID id);

   /// Add a token-based replacement fix-it to the currently-active
   /// diagnostic.
   template <typename... ArgTypes>
   InFlightDiagnostic &
   fixItReplace(SourceRange R, StructuredFixIt<ArgTypes...> fixIt,
                typename detail::PassArgument<ArgTypes>::type... VArgs) {
      DiagnosticArgument DiagArgs[] = { std::move(VArgs)... };
      return fixItReplace(R, fixItStringFor(fixIt.ID), DiagArgs);
   }

   /// Add a character-based replacement fix-it to the currently-active
   /// diagnostic.
   template <typename... ArgTypes>
   InFlightDiagnostic &
   fixItReplaceChars(SourceLoc Start, SourceLoc End,
                     StructuredFixIt<ArgTypes...> fixIt,
                     typename detail::PassArgument<ArgTypes>::type... VArgs) {
      DiagnosticArgument DiagArgs[] = { std::move(VArgs)... };
      return fixItReplaceChars(Start, End, fixItStringFor(fixIt.ID), DiagArgs);
   }

   /// Add an insertion fix-it to the currently-active diagnostic.
   template <typename... ArgTypes>
   InFlightDiagnostic &
   fixItInsert(SourceLoc L, StructuredFixIt<ArgTypes...> fixIt,
               typename detail::PassArgument<ArgTypes>::type... VArgs) {
      DiagnosticArgument DiagArgs[] = { std::move(VArgs)... };
      return fixItReplaceChars(L, L, fixItStringFor(fixIt.ID), DiagArgs);
   }

   /// Add an insertion fix-it to the currently-active diagnostic.  The
   /// text is inserted immediately *after* the token specified.
   template <typename... ArgTypes>
   InFlightDiagnostic &
   fixItInsertAfter(SourceLoc L, StructuredFixIt<ArgTypes...> fixIt,
                    typename detail::PassArgument<ArgTypes>::type... VArgs) {
      DiagnosticArgument DiagArgs[] = { std::move(VArgs)... };
      return fixItInsertAfter(L, fixItStringFor(fixIt.ID), DiagArgs);
   }

   /// Add a token-based replacement fix-it to the currently-active
   /// diagnostic.
   InFlightDiagnostic &fixItReplace(SourceRange R, StringRef Str);

   /// Add a character-based replacement fix-it to the currently-active
   /// diagnostic.
   InFlightDiagnostic &fixItReplaceChars(SourceLoc Start, SourceLoc End,
                                         StringRef Str) {
      return fixItReplaceChars(Start, End, "%0", {Str});
   }

   /// Add an insertion fix-it to the currently-active diagnostic.
   InFlightDiagnostic &fixItInsert(SourceLoc L, StringRef Str) {
      return fixItReplaceChars(L, L, "%0", {Str});
   }

   /// Add an insertion fix-it to the currently-active diagnostic.  The
   /// text is inserted immediately *after* the token specified.
   InFlightDiagnostic &fixItInsertAfter(SourceLoc L, StringRef Str) {
      return fixItInsertAfter(L, "%0", {Str});
   }

   /// Add a token-based removal fix-it to the currently-active
   /// diagnostic.
   InFlightDiagnostic &fixItRemove(SourceRange R);

   /// Add a character-based removal fix-it to the currently-active
   /// diagnostic.
   InFlightDiagnostic &fixItRemoveChars(SourceLoc Start, SourceLoc End) {
      return fixItReplaceChars(Start, End, {});
   }

   /// Add two replacement fix-it exchanging source ranges to the
   /// currently-active diagnostic.
   InFlightDiagnostic &fixItExchange(SourceRange R1, SourceRange R2);

private:
   InFlightDiagnostic &fixItReplace(SourceRange R, StringRef FormatString,
                                    ArrayRef<DiagnosticArgument> Args);

   InFlightDiagnostic &fixItReplaceChars(SourceLoc Start, SourceLoc End,
                                         StringRef FormatString,
                                         ArrayRef<DiagnosticArgument> Args);

   InFlightDiagnostic &fixItInsert(SourceLoc L, StringRef FormatString,
                                   ArrayRef<DiagnosticArgument> Args) {
      return fixItReplaceChars(L, L, FormatString, Args);
   }

   InFlightDiagnostic &fixItInsertAfter(SourceLoc L, StringRef FormatString,
                                        ArrayRef<DiagnosticArgument> Args);
};

/// Class to track, map, and remap diagnostic severity and fatality
///
class DiagnosticState {
public:
   /// Describes the current behavior to take with a diagnostic
   enum class Behavior : uint8_t {
      Unspecified,
      Ignore,
      Note,
      Remark,
      Warning,
      Error,
      Fatal,
   };

private:
   /// Whether we should continue to emit diagnostics, even after a
   /// fatal error
   bool showDiagnosticsAfterFatalError = false;

   /// Don't emit any warnings
   bool suppressWarnings = false;

   /// Emit all warnings as errors
   bool warningsAsErrors = false;

   /// Whether a fatal error has occurred
   bool fatalErrorOccurred = false;

   /// Whether any error diagnostics have been emitted.
   bool anyErrorOccurred = false;

   /// Track the previous emitted Behavior, useful for notes
   Behavior previousBehavior = Behavior::Unspecified;

   /// Track settable, per-diagnostic state that we store
   std::vector<Behavior> perDiagnosticBehavior;

public:
   DiagnosticState();

   /// Figure out the Behavior for the given diagnostic, taking current
   /// state such as fatality into account.
   Behavior determineBehavior(DiagID id);

   bool hadAnyError() const { return anyErrorOccurred; }
   bool hasFatalErrorOccurred() const { return fatalErrorOccurred; }

   void setShowDiagnosticsAfterFatalError(bool val = true) {
      showDiagnosticsAfterFatalError = val;
   }
   bool getShowDiagnosticsAfterFatalError() {
      return showDiagnosticsAfterFatalError;
   }

   /// Whether to skip emitting warnings
   void setSuppressWarnings(bool val) { suppressWarnings = val; }
   bool getSuppressWarnings() const { return suppressWarnings; }

   /// Whether to treat warnings as errors
   void setWarningsAsErrors(bool val) { warningsAsErrors = val; }
   bool getWarningsAsErrors() const { return warningsAsErrors; }

   void resetHadAnyError() {
      anyErrorOccurred = false;
      fatalErrorOccurred = false;
   }

   /// Set per-diagnostic behavior
   void setDiagnosticBehavior(DiagID id, Behavior behavior) {
      perDiagnosticBehavior[(unsigned)id] = behavior;
   }

private:
   // Make the state movable only
   DiagnosticState(const DiagnosticState &) = delete;
   const DiagnosticState &operator=(const DiagnosticState &) = delete;

   DiagnosticState(DiagnosticState &&) = default;
   DiagnosticState &operator=(DiagnosticState &&) = default;
};

/// Class responsible for formatting diagnostics and presenting them
/// to the user.
class DiagnosticEngine {
public:
   /// The source manager used to interpret source locations and
   /// display diagnostics.
   SourceManager &SourceMgr;

private:
   /// The diagnostic consumer(s) that will be responsible for actually
   /// emitting diagnostics.
   SmallVector<DiagnosticConsumer *, 2> Consumers;

   /// Tracks diagnostic behaviors and state
   DiagnosticState state;

   /// The currently active diagnostic, if there is one.
   Optional<Diagnostic> ActiveDiagnostic;

   /// All diagnostics that have are no longer active but have not yet
   /// been emitted due to an open transaction.
   SmallVector<Diagnostic, 4> TentativeDiagnostics;

   /// The set of declarations for which we have pretty-printed
   /// results that we can point to on the command line.
   llvm::DenseMap<const Decl *, SourceLoc> PrettyPrintedDeclarations;

   llvm::BumpPtrAllocator TransactionAllocator;
   /// A set of all strings involved in current transactional chain.
   /// This is required because diagnostics are not directly emitted
   /// but rather stored until all transactions complete.
   llvm::StringSet<llvm::BumpPtrAllocator &> TransactionStrings;

   /// The number of open diagnostic transactions. Diagnostics are only
   /// emitted once all transactions have closed.
   unsigned TransactionCount = 0;

   /// For batch mode, use this to know where to output a diagnostic from a
   /// non-primary file. It's any location in the buffer of the current primary
   /// input being compiled.
   /// May be invalid.
   SourceLoc bufferIndirectlyCausingDiagnostic;

   /// Print diagnostic names after their messages
   bool printDiagnosticNames = false;

   /// Use descriptive diagnostic style when available.
   bool useDescriptiveDiagnostics = false;

   /// Path to diagnostic documentation directory.
   std::string diagnosticDocumentationPath = "";

   friend class InFlightDiagnostic;
   friend class DiagnosticTransaction;
   friend class CompoundDiagnosticTransaction;

public:
   explicit DiagnosticEngine(SourceManager &SourceMgr)
      : SourceMgr(SourceMgr), ActiveDiagnostic(),
        TransactionStrings(TransactionAllocator) {}

   /// hadAnyError - return true if any *error* diagnostics have been emitted.
   bool hadAnyError() const { return state.hadAnyError(); }

   bool hasFatalErrorOccurred() const {
      return state.hasFatalErrorOccurred();
   }

   void setShowDiagnosticsAfterFatalError(bool val = true) {
      state.setShowDiagnosticsAfterFatalError(val);
   }
   bool getShowDiagnosticsAfterFatalError() {
      return state.getShowDiagnosticsAfterFatalError();
   }

   /// Whether to skip emitting warnings
   void setSuppressWarnings(bool val) { state.setSuppressWarnings(val); }
   bool getSuppressWarnings() const {
      return state.getSuppressWarnings();
   }

   /// Whether to treat warnings as errors
   void setWarningsAsErrors(bool val) { state.setWarningsAsErrors(val); }
   bool getWarningsAsErrors() const {
      return state.getWarningsAsErrors();
   }

   /// Whether to print diagnostic names after their messages
   void setPrintDiagnosticNames(bool val) {
      printDiagnosticNames = val;
   }
   bool getPrintDiagnosticNames() const {
      return printDiagnosticNames;
   }

   void setUseDescriptiveDiagnostics(bool val) {
      useDescriptiveDiagnostics = val;
   }
   bool getUseDescriptiveDiagnostics() const {
      return useDescriptiveDiagnostics;
   }

   void setDiagnosticDocumentationPath(std::string path) {
      diagnosticDocumentationPath = path;
   }
   StringRef getDiagnosticDocumentationPath() {
      return diagnosticDocumentationPath;
   }

   void ignoreDiagnostic(DiagID id) {
      state.setDiagnosticBehavior(id, DiagnosticState::Behavior::Ignore);
   }

   void resetHadAnyError() {
      state.resetHadAnyError();
   }

   /// Add an additional DiagnosticConsumer to receive diagnostics.
   void addConsumer(DiagnosticConsumer &Consumer) {
      Consumers.push_back(&Consumer);
   }

   /// Remove a specific DiagnosticConsumer.
   void removeConsumer(DiagnosticConsumer &Consumer) {
      Consumers.erase(
         std::remove(Consumers.begin(), Consumers.end(), &Consumer));
   }

   /// Remove and return all \c DiagnosticConsumers.
   std::vector<DiagnosticConsumer *> takeConsumers() {
      auto Result = std::vector<DiagnosticConsumer*>(Consumers.begin(),
                                                     Consumers.end());
      Consumers.clear();
      return Result;
   }

   /// Return all \c DiagnosticConsumers.
   ArrayRef<DiagnosticConsumer *> getConsumers() const {
      return Consumers;
   }

   /// Emit a diagnostic using a preformatted array of diagnostic
   /// arguments.
   ///
   /// \param Loc The location to which the diagnostic refers in the source
   /// code.
   ///
   /// \param ID The diagnostic ID.
   ///
   /// \param Args The preformatted set of diagnostic arguments. The caller
   /// must ensure that the diagnostic arguments have the appropriate type.
   ///
   /// \returns An in-flight diagnostic, to which additional information can
   /// be attached.
   InFlightDiagnostic diagnose(SourceLoc Loc, DiagID ID,
                               ArrayRef<DiagnosticArgument> Args) {
      return diagnose(Loc, Diagnostic(ID, Args));
   }

   /// Emit a diagnostic using a preformatted array of diagnostic
   /// arguments.
   ///
   /// \param Loc The declaration name location to which the
   /// diagnostic refers in the source code.
   ///
   /// \param ID The diagnostic ID.
   ///
   /// \param Args The preformatted set of diagnostic arguments. The caller
   /// must ensure that the diagnostic arguments have the appropriate type.
   ///
   /// \returns An in-flight diagnostic, to which additional information can
   /// be attached.
   InFlightDiagnostic diagnose(DeclNameLoc Loc, DiagID ID,
                               ArrayRef<DiagnosticArgument> Args) {
      return diagnose(Loc.getBaseNameLoc(), Diagnostic(ID, Args));
   }

   /// Emit an already-constructed diagnostic at the given location.
   ///
   /// \param Loc The location to which the diagnostic refers in the source
   /// code.
   ///
   /// \param D The diagnostic.
   ///
   /// \returns An in-flight diagnostic, to which additional information can
   /// be attached.
   InFlightDiagnostic diagnose(SourceLoc Loc, const Diagnostic &D) {
      assert(!ActiveDiagnostic && "Already have an active diagnostic");
      ActiveDiagnostic = D;
      ActiveDiagnostic->setLoc(Loc);
      return InFlightDiagnostic(*this);
   }

   /// Emit a diagnostic with the given set of diagnostic arguments.
   ///
   /// \param Loc The location to which the diagnostic refers in the source
   /// code.
   ///
   /// \param ID The diagnostic to be emitted.
   ///
   /// \param Args The diagnostic arguments, which will be converted to
   /// the types expected by the diagnostic \p ID.
   template<typename ...ArgTypes>
   InFlightDiagnostic
   diagnose(SourceLoc Loc, Diag<ArgTypes...> ID,
            typename detail::PassArgument<ArgTypes>::type... Args) {
      return diagnose(Loc, Diagnostic(ID, std::move(Args)...));
   }

   /// Delete an API that may lead clients to avoid specifying source location.
   template<typename ...ArgTypes>
   InFlightDiagnostic
   diagnose(Diag<ArgTypes...> ID,
            typename detail::PassArgument<ArgTypes>::type... Args) = delete;

   /// Emit a diagnostic with the given set of diagnostic arguments.
   ///
   /// \param Loc The declaration name location to which the
   /// diagnostic refers in the source code.
   ///
   /// \param ID The diagnostic to be emitted.
   ///
   /// \param Args The diagnostic arguments, which will be converted to
   /// the types expected by the diagnostic \p ID.
   template<typename ...ArgTypes>
   InFlightDiagnostic
   diagnose(DeclNameLoc Loc, Diag<ArgTypes...> ID,
            typename detail::PassArgument<ArgTypes>::type... Args) {
      return diagnose(Loc.getBaseNameLoc(), Diagnostic(ID, std::move(Args)...));
   }

   /// Emit a diagnostic using a preformatted array of diagnostic
   /// arguments.
   ///
   /// \param decl The declaration to which this diagnostic refers, which
   /// may or may not have associated source-location information.
   ///
   /// \param id The diagnostic ID.
   ///
   /// \param args The preformatted set of diagnostic arguments. The caller
   /// must ensure that the diagnostic arguments have the appropriate type.
   ///
   /// \returns An in-flight diagnostic, to which additional information can
   /// be attached.
   InFlightDiagnostic diagnose(const Decl *decl, DiagID id,
                               ArrayRef<DiagnosticArgument> args) {
      return diagnose(decl, Diagnostic(id, args));
   }

   /// Emit an already-constructed diagnostic referencing the given
   /// declaration.
   ///
   /// \param decl The declaration to which this diagnostic refers, which
   /// may or may not have associated source-location information.
   ///
   /// \param diag The diagnostic.
   ///
   /// \returns An in-flight diagnostic, to which additional information can
   /// be attached.
   InFlightDiagnostic diagnose(const Decl *decl, const Diagnostic &diag) {
      assert(!ActiveDiagnostic && "Already have an active diagnostic");
      ActiveDiagnostic = diag;
      ActiveDiagnostic->setDecl(decl);
      return InFlightDiagnostic(*this);
   }

   /// Emit a diagnostic with the given set of diagnostic arguments.
   ///
   /// \param decl The declaration to which this diagnostic refers, which
   /// may or may not have associated source-location information.
   ///
   /// \param id The diagnostic to be emitted.
   ///
   /// \param args The diagnostic arguments, which will be converted to
   /// the types expected by the diagnostic \p ID.
   template<typename ...ArgTypes>
   InFlightDiagnostic
   diagnose(const Decl *decl, Diag<ArgTypes...> id,
            typename detail::PassArgument<ArgTypes>::type... args) {
      return diagnose(decl, Diagnostic(id, std::move(args)...));
   }

   /// Emit a parent diagnostic and attached notes.
   ///
   /// \param parentDiag An InFlightDiagnostic representing the parent diag.
   ///
   /// \param builder A closure which builds and emits notes to be attached to
   /// the parent diag.
   void diagnoseWithNotes(InFlightDiagnostic parentDiag,
                          llvm::function_ref<void(void)> builder);

   /// \returns true if diagnostic is marked with PointsToFirstBadToken
   /// option.
   bool isDiagnosticPointsToFirstBadToken(DiagID id) const;

   /// \returns true if any diagnostic consumer gave an error while invoking
   //// \c finishProcessing.
   bool finishProcessing();

   /// Format the given diagnostic text and place the result in the given
   /// buffer.
   static void formatDiagnosticText(
      llvm::raw_ostream &Out, StringRef InText,
      ArrayRef<DiagnosticArgument> FormatArgs,
      DiagnosticFormatOptions FormatOpts = DiagnosticFormatOptions());

private:
   /// Called when tentative diagnostic is about to be flushed,
   /// to apply any required transformations e.g. copy string arguments
   /// to extend their lifetime.
   void onTentativeDiagnosticFlush(Diagnostic &diagnostic);

   /// Flush the active diagnostic.
   void flushActiveDiagnostic();

   /// Retrieve the active diagnostic.
   Diagnostic &getActiveDiagnostic() { return *ActiveDiagnostic; }

   /// Generate DiagnosticInfo for a Diagnostic to be passed to consumers.
   Optional<DiagnosticInfo>
   diagnosticInfoForDiagnostic(const Diagnostic &diagnostic);

   /// Send \c diag to all diagnostic consumers.
   void emitDiagnostic(const Diagnostic &diag);

   /// Send all tentative diagnostics to all diagnostic consumers and
   /// delete them.
   void emitTentativeDiagnostics();

public:
   static const char *diagnosticStringFor(const DiagID id,
                                          bool printDiagnosticName);

   /// If there is no clear .dia file for a diagnostic, put it in the one
   /// corresponding to the SourceLoc given here.
   /// In particular, in batch mode when a diagnostic is located in
   /// a non-primary file, use this affordance to place it in the .dia
   /// file for the primary that is currently being worked on.
   void setBufferIndirectlyCausingDiagnosticToInput(SourceLoc);
   void resetBufferIndirectlyCausingDiagnostic();
   SourceLoc getDefaultDiagnosticLoc() const {
      return bufferIndirectlyCausingDiagnostic;
   }
};

class BufferIndirectlyCausingDiagnosticRAII {
private:
   DiagnosticEngine &Diags;
public:
   BufferIndirectlyCausingDiagnosticRAII(const SourceFile &SF);
   ~BufferIndirectlyCausingDiagnosticRAII() {
      Diags.resetBufferIndirectlyCausingDiagnostic();
   }
};

/// Represents a diagnostic transaction. While a transaction is
/// open, all recorded diagnostics are saved until the transaction commits,
/// at which point they are emitted. If the transaction is instead aborted,
/// the diagnostics are erased. Transactions may be nested but must be closed
/// in LIFO order. An open transaction is implicitly committed upon
/// destruction.
class DiagnosticTransaction {
protected:
   DiagnosticEngine &Engine;

   /// How many tentative diagnostics there were when the transaction
   /// was opened.
   unsigned PrevDiagnostics;

   /// How many other transactions were open when this transaction was
   /// opened.
   unsigned Depth;

   /// Whether this transaction is currently open.
   bool IsOpen = true;

public:
   DiagnosticTransaction(const DiagnosticTransaction &) = delete;
   DiagnosticTransaction &operator=(const DiagnosticTransaction &) = delete;

   explicit DiagnosticTransaction(DiagnosticEngine &engine)
      : Engine(engine),
        PrevDiagnostics(Engine.TentativeDiagnostics.size()),
        Depth(Engine.TransactionCount),
        IsOpen(true)
   {
      Engine.TransactionCount++;
   }

   ~DiagnosticTransaction() {
      if (IsOpen) {
         commit();
      }

      if (Depth == 0) {
         Engine.TransactionStrings.clear();
         Engine.TransactionAllocator.Reset();
      }
   }

   /// Abort and close this transaction and erase all diagnostics
   /// record while it was open.
   void abort() {
      close();
      Engine.TentativeDiagnostics.erase(
         Engine.TentativeDiagnostics.begin() + PrevDiagnostics,
         Engine.TentativeDiagnostics.end());
   }

   /// Commit and close this transaction. If this is the top-level
   /// transaction, emit any diagnostics that were recorded while it was open.
   void commit() {
      close();
      if (Depth == 0) {
         assert(PrevDiagnostics == 0);
         Engine.emitTentativeDiagnostics();
      }
   }

private:
   void close() {
      assert(IsOpen && "only open transactions may be closed");
      IsOpen = false;
      Engine.TransactionCount--;
      assert(Depth == Engine.TransactionCount &&
             "transactions must be closed LIFO");
   }
};

/// Represents a diagnostic transaction which constructs a compound diagnostic
/// from any diagnostics emitted inside. A compound diagnostic consists of a
/// parent error, warning, or remark followed by a variable number of child
/// notes. The semantics are otherwise the same as a regular
/// DiagnosticTransaction.
class CompoundDiagnosticTransaction : public DiagnosticTransaction {
public:
   explicit CompoundDiagnosticTransaction(DiagnosticEngine &engine)
      : DiagnosticTransaction(engine) {}

   ~CompoundDiagnosticTransaction() {
      if (IsOpen) {
         commit();
      }

      if (Depth == 0) {
         Engine.TransactionStrings.clear();
         Engine.TransactionAllocator.Reset();
      }
   }

   void commit() {
      assert(PrevDiagnostics < Engine.TentativeDiagnostics.size() &&
             "CompoundDiagnosticTransaction must contain at least one diag");

      // The first diagnostic is assumed to be the parent. If this is not an
      // error or warning, we'll assert later when trying to add children.
      Diagnostic &parent = Engine.TentativeDiagnostics[PrevDiagnostics];

      // Associate the children with the parent.
      for (auto diag =
         Engine.TentativeDiagnostics.begin() + PrevDiagnostics + 1;
           diag != Engine.TentativeDiagnostics.end(); ++diag) {
         diag->setIsChildNote(true);
         parent.addChildNote(std::move(*diag));
      }

      // Erase the children, they'll be emitted alongside their parent.
      Engine.TentativeDiagnostics.erase(Engine.TentativeDiagnostics.begin() +
                                        PrevDiagnostics + 1,
                                        Engine.TentativeDiagnostics.end());

      DiagnosticTransaction::commit();
   }
};

inline void
DiagnosticEngine::diagnoseWithNotes(InFlightDiagnostic parentDiag,
                                    llvm::function_ref<void(void)> builder) {
   CompoundDiagnosticTransaction transaction(*this);
   parentDiag.flush();
   builder();
}

} // end namespace polar

#endif
